package com.stars.easyms.jwt.provider;

import com.alibaba.fastjson.JSON;
import com.stars.easyms.base.constant.CommonConstants;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.util.PatternMatcherUtil;
import com.stars.easyms.jwt.exception.EasyMsJwtException;
import com.stars.easyms.jwt.properties.EasyMsJwtProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.*;

/**
 * <p>interfaceName: EasyMsJwtProvider</p>
 * <p>description: jwt提供者</p>
 *
 * @author guoguifang
 * @version 1.6.1
 * @date 2020-08-18 10:10
 */
@Slf4j
public final class EasyMsJwtProvider {

    private Key key;

    private long tokenValidityInMilliseconds;

    private long sessionValidityInMilliseconds;

    private long sessionValidityInMillisecondsForRememberMe;

    private EasyMsJwtProperties easyMsJwtProperties;

    private final Set<String> permitUrlSet = new HashSet<>();

    private boolean initSuccess;

    public EasyMsJwtProvider(EasyMsJwtProperties easyMsJwtProperties) {
        this.easyMsJwtProperties = easyMsJwtProperties;
    }

    @PostConstruct
    private void init() {
        if (!easyMsJwtProperties.isEnabled()) {
            log.warn("JWT is not enabled !");
            return;
        }

        // 获取token的秘钥和过期时间
        byte[] keyBytes;
        String secret;
        if (StringUtils.hasText(secret = easyMsJwtProperties.getSecret())) {
            log.warn("Warning: the JWT key used is not Base64-encoded. We recommend using the `{}.base64-secret` key for optimum security.",
                    EasyMsJwtProperties.JWT_PREFIX);
            keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        } else if (StringUtils.hasText(secret = easyMsJwtProperties.getBase64Secret())) {
            if (log.isDebugEnabled()) {
                log.debug("Using a Base64-encoded JWT secret key");
            }
            keyBytes = Decoders.BASE64.decode(secret);
        } else {
            throw new EasyMsJwtException("The JWT key not found! Please config {}.secret or {}.base64-secret!",
                    EasyMsJwtProperties.JWT_PREFIX, EasyMsJwtProperties.JWT_PREFIX);
        }
        this.key = Keys.hmacShaKeyFor(keyBytes);
        this.tokenValidityInMilliseconds = 1000 * easyMsJwtProperties.getTokenValidityInSeconds();
        this.sessionValidityInMilliseconds = 1000 * easyMsJwtProperties.getSessionValidityInSeconds();
        this.sessionValidityInMillisecondsForRememberMe = 1000 * easyMsJwtProperties.getSessionValidityInSecondsForRememberMe();

        // 获取不需要做jwt验证的url的集合
        if (StringUtils.hasText(easyMsJwtProperties.getPermit())) {
            Collections.addAll(permitUrlSet,
                    StringUtils.tokenizeToStringArray(easyMsJwtProperties.getPermit(), CommonConstants.PROPERTIES_SEPARATOR));
        }

        this.initSuccess = true;
        log.info("JWT enabled successfully !");
    }

    /**
     * 创建一个token
     *
     * @param userInfo 用户信息
     * @return token
     */
    @Nullable
    public String createToken(Map<String, Object> userInfo) {
        return this.createToken(JSON.toJSONString(userInfo));
    }

    /**
     * 创建一个token
     *
     * @param userInfoStr 用户信息字符串
     * @return token
     */
    @Nullable
    public String createToken(String userInfoStr) {
        if (this.isValid()) {
            // header：指定类型
            Map<String, Object> header = new HashMap<>(4);
            header.put(Header.TYPE, Header.JWT_TYPE);

            // 发行时间、过期时间
            Date iat = new Date();
            Date expireDate = new Date(iat.getTime() + this.tokenValidityInMilliseconds);

            return HttpHeaderConstants.HEADER_TOKEN_PREFIX + Jwts
                    .builder()
                    .setHeader(header)
                    .setIssuer(easyMsJwtProperties.getIssuer())
                    .claim(HttpHeaderConstants.HEADER_KEY_USER_INFO, userInfoStr)
                    .signWith(key, SignatureAlgorithm.HS512)
                    .setIssuedAt(iat)
                    .setExpiration(expireDate)
                    .compact();
        }
        return null;
    }

    /**
     * 创建一个刷新token，当普通token过期后使用刷新token来重新创建普通token
     *
     * @param userInfo   用户信息
     * @param rememberMe 是否记住我，如果是则token有效期较长
     * @return 刷新token
     */
    @Nullable
    public String createRefreshToken(Map<String, Object> userInfo, boolean rememberMe) {
        return this.createRefreshToken(JSON.toJSONString(userInfo), rememberMe);
    }

    /**
     * 创建一个刷新token，当普通token过期后使用刷新token来重新创建普通token
     *
     * @param userInfoStr 用户信息字符串
     * @param rememberMe  是否记住我，如果是则token有效期较长
     * @return 刷新token
     */
    @Nullable
    public String createRefreshToken(String userInfoStr, boolean rememberMe) {
        if (this.isValid()) {
            // header：指定类型
            Map<String, Object> header = new HashMap<>(4);
            header.put(Header.TYPE, Header.JWT_TYPE);

            // 发行时间、过期时间
            Date iat = new Date();
            Date expireDate;
            if (rememberMe) {
                expireDate = new Date(iat.getTime() + this.tokenValidityInMilliseconds + this.sessionValidityInMillisecondsForRememberMe);
            } else {
                expireDate = new Date(iat.getTime() + this.tokenValidityInMilliseconds + this.sessionValidityInMilliseconds);
            }

            return HttpHeaderConstants.HEADER_TOKEN_PREFIX + Jwts
                    .builder()
                    .setHeader(header)
                    .setIssuer(easyMsJwtProperties.getIssuer())
                    .claim(HttpHeaderConstants.HEADER_KEY_USER_INFO, userInfoStr)
                    .claim(HttpHeaderConstants.HEADER_KEY_REMEMBER_ME, rememberMe)
                    .signWith(key, SignatureAlgorithm.HS512)
                    .setIssuedAt(iat)
                    .setExpiration(expireDate)
                    .compact();
        }
        return null;
    }

    /**
     * 校验token是否有效并返回用户信息字符串
     *
     * @param bearerToken token
     * @return 用户信息字符串
     */
    @Nullable
    public String getAuthentication(String bearerToken) {
        Claims claims = getClaims(bearerToken);
        if (claims != null) {
            return (String) claims.get(HttpHeaderConstants.HEADER_KEY_USER_INFO);
        }
        return null;
    }

    /**
     * 返回用户信息字符串
     *
     * @return 用户信息字符串
     */
    @Nullable
    public String getAuthentication(Claims claims) {
        if (claims != null) {
            return (String) claims.get(HttpHeaderConstants.HEADER_KEY_USER_INFO);
        }
        return null;
    }

    /**
     * 判断是否选择了记住我
     */
    public boolean isRememberMe(Claims claims) {
        if (claims != null) {
            Object rememberMeObj = claims.get(HttpHeaderConstants.HEADER_KEY_REMEMBER_ME);
            if (rememberMeObj != null) {
                return Boolean.parseBoolean(rememberMeObj.toString());
            }
        }
        return false;
    }

    /**
     * 校验token是否有效并返回Claims信息
     *
     * @param bearerToken token
     * @return Claims信息
     */
    public Claims getClaims(String bearerToken) {
        if (easyMsJwtProperties.isEnabled()) {
            try {
                if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HttpHeaderConstants.HEADER_TOKEN_PREFIX)) {
                    String token = bearerToken.substring(HttpHeaderConstants.HEADER_TOKEN_PREFIX.length());
                    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
                }
            } catch (JwtException | IllegalArgumentException e) {
                log.info("Invalid JWT token: {}", e.getMessage());
            }
        }
        return null;
    }

    public boolean isValid() {
        return easyMsJwtProperties.isEnabled() && this.initSuccess;
    }

    /**
     * 获取token的生效时间(单位：毫秒)
     */
    public long getTokenValidityInMilliseconds() {
        return tokenValidityInMilliseconds;
    }

    /**
     * 获取session的生效时间(单位：毫秒)
     */
    public long getSessionValidityInMilliseconds() {
        return sessionValidityInMilliseconds;
    }

    /**
     * 获取记住我情况下的session的生效时间(单位:毫秒)
     */
    public long getSessionValidityInMillisecondsForRememberMe() {
        return sessionValidityInMillisecondsForRememberMe;
    }

    /**
     * 判断请求是否需要jwt验证
     *
     * @param requestPath 请求路径
     * @return 是否需要验证jwt(true : 是 ， false : 否)
     */
    public boolean isNeedAuthenticate(String requestPath) {
        return this.isValid() && !isPermitUrl(requestPath);
    }

    private boolean isPermitUrl(String requestPath) {
        for (String permitUrl : permitUrlSet) {
            if (PatternMatcherUtil.doUrlPatternMatch(permitUrl, requestPath, true)) {
                return true;
            }
        }
        return false;
    }
}
